package md.rdcrng.cvs;

import java.io.Serializable;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.SessionScoped;
import javax.enterprise.context.spi.Contextual;
import javax.enterprise.context.spi.CreationalContext;
import javax.faces.component.UIViewRoot;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.PreDestroyViewMapEvent;
import javax.faces.event.SystemEvent;
import javax.faces.event.ViewMapListener;

/**
 <p/>
 @author Radu Creanga <rdcrng@gmail.com>
 */
@SessionScoped
class ViewStorage implements ViewMapListener, Serializable {

    private static final long serialVersionUID = 42L;
    private static final String VIEW_ID = ViewContext.class.getName() + ".VIEW_ID";
    private final ConcurrentMap<UUID, Components> storage = new ConcurrentHashMap<>();

    private UUID getViewId() {
        return this.getViewId(Faces.getViewMap());
    }

    private UUID getViewId(Map<String, Object> viewMap) {
        Object id = viewMap.get(ViewStorage.VIEW_ID);
        if (id instanceof UUID) {
            return UUID.class.cast(id);
        }
        UUID uuid = UUID.randomUUID();
        viewMap.put(ViewStorage.VIEW_ID, uuid);
        return uuid;
    }

    @PostConstruct
    protected void postConstruct() {
        Faces.getApplication().subscribeToEvent(PreDestroyViewMapEvent.class, UIViewRoot.class, this);
    }

    @PreDestroy
    protected void preDestroy() {
        for (Components components : this.storage.values()) {
            components.destroy();
        }
    }

    protected <T> T create(Contextual<T> contextual, CreationalContext<T> creationalContext) {
        UUID viewId = this.getViewId();
        if (!this.storage.containsKey(viewId)) {
            this.storage.put(viewId, new Components());
        }
        Components components = this.storage.get(viewId);
        return components.create(contextual, creationalContext);
    }

    protected <T> T get(Contextual<T> contextual) {
        Components components = this.storage.get(this.getViewId());
        return components == null ? null : components.get(contextual);
    }

    @Override
    public void processEvent(SystemEvent event) throws AbortProcessingException {
        if (event instanceof PreDestroyViewMapEvent) {
            UIViewRoot viewRoot = (UIViewRoot) event.getSource();
            UUID viewId = this.getViewId(viewRoot.getViewMap());
            Components components = this.storage.remove(viewId);
            if (components != null) {
                components.destroy();
            }
        }
    }

    @Override
    public synchronized boolean isListenerForSource(Object source) {
        return source instanceof UIViewRoot;
    }

}
